#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
# - http://heavyz.blogspot.com/
# - http://buggarden.blogspot.com/
#
# Views for the Foods N' Roses webapp.
#
# Created on 2008-08-15.
# $Id$
#

import logging
import urllib

from django.template import loader
from django.template import Context
from django.http import HttpRequest
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.http import HttpResponseNotFound

from google.appengine.api import users
from google.appengine.api import memcache

from buggarden.apps.google_services import AjaxApis
from buggarden.apps.google_services import WebmastersTools
from buggarden.apps.google_services import Analytics

from buggarden.apps.fnr.config import Config
from buggarden.apps.fnr.models import Resto
from buggarden.apps.fnr.models import RestoTag
from buggarden.apps.fnr.models import RestoComment

from buggarden.apps.fnr import utils
from buggarden.apps.fnr import ajax

#---------------------------------------------------------------------------------------------------
# Constants
#---------------------------------------------------------------------------------------------------

LANGUAGE_SESSION_NAME = "django_language"

#---------------------------------------------------------------------------------------------------
# Common functions
#---------------------------------------------------------------------------------------------------

class Language(object):

    SUPPORTED_LANG_CODES = ("en", "fr", "zh-cn",)
    
    def __init__(self, code, url, is_current):
        self.code = code
        self.url = url
        self.is_current = is_current


def _redirect_to_error_page(error):
    params = { "error" : error, }
    url = "%s/error/?%s" % (Config.URL_PREFIX, urllib.urlencode(params))
    return HttpResponseRedirect(url)


def _create_lang_url(redirect_url, lang_code):
    params = { "url" : redirect_url, "lang" : lang_code, }
    url = "%s/lang/?%s" % (Config.URL_PREFIX, urllib.urlencode(params))
    return url


def _get_common_context_dict(request):
    user = users.get_current_user()
    if user is None:
        log_url  = users.create_login_url(request.path)
        log_text = "Login"
        is_admin = False
    else:
        log_url  = users.create_logout_url(request.path)
        log_text = "Logout"
        if user in Config.ADMINS:
            is_admin = True
        else:
            is_admin = False
    
    try:
        current_lang_code = request.session.get(LANGUAGE_SESSION_NAME, None)
    except AttributeError:
        current_lang_code = None
    languages = []
    for lang_code in Language.SUPPORTED_LANG_CODES:
        lang_url    = _create_lang_url(request.path, lang_code)
        is_current  = (lang_code == current_lang_code)
        languages.append(Language(lang_code, lang_url, is_current))
    
    return { "user"               : user,
             "log_url"            : log_url,
             "log_text"           : log_text,
             "is_admin"           : is_admin,
             "languages"          : languages,
             "g_ajax_apis"        : AjaxApis.key(request),
             "g_webmasters_tools" : WebmastersTools.key(request),
             "g_analytics"        : Analytics.key(request), }


#---------------------------------------------------------------------------------------------------
# Home page, static pages, 404 not-found page, change language and error page
#---------------------------------------------------------------------------------------------------

def home_page(request):
    """Displays the Foods N' Roses webapp's home page.
    """
    
    tags = RestoTag.gql("ORDER BY count DESC").fetch(20, 0)
    vals = { "tags" : tags, }
    vals.update(_get_common_context_dict(request))
    ctxt = Context(vals)
    tmpl = loader.get_template("%s/home.html" % Config.APP_NAME)
    return HttpResponse(tmpl.render(ctxt))


def static_page(request, name):
    """Displays the requested static page.
    """
    
    STATIC_PAGE_NAMES = ( "about", "help", "contact", )
    if name in STATIC_PAGE_NAMES:
        vals = _get_common_context_dict(request)
        ctxt = Context(vals)
        tmpl = loader.get_template("%s/%s.html" % (Config.APP_NAME, name))
        return HttpResponse(tmpl.render(ctxt))
    else:
        return not_found(request)


def change_language(request):
    """Change the current language.
    """
    
    redirect_url = utils.get_param(request.REQUEST, "url" )
    lang_code    = utils.get_param(request.REQUEST, "lang")
    try:
        request.session[LANGUAGE_SESSION_NAME] = lang_code
    except AttributeError:
        pass
    return HttpResponseRedirect(redirect_url)


def not_found_page(request):
    """Displays the 404 not-found page.
    """
    
    vals = { "url" : request.path, }
    vals.update(_get_common_context_dict(request))
    ctxt = Context(vals)
    tmpl = loader.get_template("%s/not_found.html" % Config.APP_NAME)
    return HttpResponseNotFound(tmpl.render(ctxt))


def error_page(request):
    """Displays the error page.
    """
    
    vals = { "error" : utils.get_param(request.REQUEST, "error"), }
    vals.update(_get_common_context_dict(request))
    ctxt = Context(vals)
    tmpl = loader.get_template("%s/error.html" % Config.APP_NAME)
    return HttpResponse(tmpl.render(ctxt))


#---------------------------------------------------------------------------------------------------
# Add a resto to datastore
#---------------------------------------------------------------------------------------------------

def add_resto(request):
    """Handles request for adding a resto. If the request method is GET, this function renders a
    registration form. If the request method is POST, this function collects information from the
    request, inserts a new resto to the datastore, and redirects to the resto's home page.
    
    This function requires that the user be logged in.
    """
    
    # Check that the user is logged in.
    if users.get_current_user() is None:
        logging.warning("Anonymous user should not have been able to invoke add_resto().")
        error = "Anonymous user should not have been able to add a resto."
        return _redirect_to_error_page(error)
    
    if request.method == "GET":
    
        # Render the registration form.
        vals = { "error" : utils.get_param(request.GET, "error"), }
        vals.update(_get_common_context_dict(request))
        ctxt = Context(vals)
        tmpl = loader.get_template("%s/add_resto.html" % Config.APP_NAME)
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == "POST":
    
        # Collect submitted information from request.
        name      = utils.get_param(request.POST, "name")
        aka       = utils.get_param(request.POST, "aka")
        address   = utils.get_param(request.POST, "address")
        post_code = utils.get_param(request.POST, "post_code")
        city      = utils.get_param(request.POST, "city")
        
        # Create a new resto, put it into datastore, and redirect to its home page.
        try:
            resto = Resto( uid=utils.generate_random_id("R"),
                           name=name,
                           aka=aka,
                           address=address,
                           post_code=post_code,
                           city=city,
                           submitter=users.get_current_user() )
            resto.put()
            url = "%s/resto/%s/" % (Config.URL_PREFIX, resto.uid)
        except Exception, ex:
            logging.error( "Failed to create Resto entity (%s): %s" % (type(ex), str(ex)) )
            params = { "error" : "Failed to add resto: %s" % str(ex), }
            url = "%s/add_resto/?%s" % (Config.URL_PREFIX, urllib.urlencode(params))
        return HttpResponseRedirect(url)

#---------------------------------------------------------------------------------------------------
# View resto catalog
#---------------------------------------------------------------------------------------------------

def view_resto_catalog(request):
    """
    """
    
    # Calculate offset from page number.
    # TODO:
    RESTOS_PER_PAGE = 20
    try:
        page = int(request.GET.get("page", "1"))
    except ValueError:
        page = 1
    if page < 1:
        page = 1
    offset = (page - 1) * RESTOS_PER_PAGE
    # Retrieve Resto entities from datastore.
    query  = Resto.gql("ORDER BY name")
    restos = query.fetch(RESTOS_PER_PAGE, offset)
    # Render.
    vals = { "restos" : restos, "page" : page, }
    vals.update(_get_common_context_dict(request))
    ctxt = Context(vals)
    tmpl = loader.get_template("%s/resto_catalog.html" % Config.APP_NAME)
    return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------
# Get resto logo as a raw image
#---------------------------------------------------------------------------------------------------

def get_resto_logo(request, uid):
    """Returns the raw logo image of a resto.
    """
    
    resto = Resto.gql("WHERE uid=:uid", uid=uid).get()
    if resto is not None:
        if resto.logo_type is not None and resto.logo_data is not None:
            return HttpResponse(resto.logo_data, mimetype=resto.logo_type)
        else:
            return HttpResponseNotFound("Resto %s does not have a logo." % resto.name)
    else:
        return HttpResponseNotFound("Cannot find resto by uid '%s'." % uid)

#---------------------------------------------------------------------------------------------------
# View resto home page
#---------------------------------------------------------------------------------------------------

def view_resto(request, uid):
    
    context_dict = _get_common_context_dict(request)
    context_dict["editable"] = True
    
    section = utils.get_param(request.REQUEST, "section")
    
    if section is None:
        # Not an AJAX request: render the resto's home page which contains default sections.
        try:
            if request.method != "GET":
                raise Exception("This view does not support '%s' request." % request.method)
            
            ACTIONS = ( ajax.RestoNamesSection,
                        ajax.RestoLogoSection,
                        ajax.RestoDescSection,
                        ajax.RestoAddressSection,
                        ajax.RestoContactSection,
                        ajax.RestoRatingSection,
                        ajax.RestoTagsSection,
                        ajax.RestoBestDishesSection,
                        ajax.RestoMapSection,
                        ajax.RestoFooterSection, )
            for action_class in ACTIONS:
                action = action_class(uid)
                action.process(context_dict, request)
            ctxt = Context(context_dict)
            tmpl = loader.get_template("%s/resto.html" % Config.APP_NAME)
            return HttpResponse(tmpl.render(ctxt))
        
        except Exception, ex:
            error = "Failed to process request for resto home page (%s): %s" % (type(ex), str(ex))
            logging.error(error)
            return _redirect_to_error_page(error)
    
    else:
        # An AJAX request: delegate to the AJAX action.
        try:
            action_class = ajax.RESTO_SECTION_MAPPINGS.get(section)
            if action_class is None:
                error = "Failed to find AJAX action class for resto section '%s'." % section
                raise Exception(error)
            else:
                action = action_class(uid)
                action.process(context_dict, request)
                ctxt = Context(context_dict)
                tmpl = loader.get_template("%s/sections/%s.html" % (Config.APP_NAME, section))
                return HttpResponse(tmpl.render(ctxt))
        
        except Exception, ex:
            error = "Failed to process request for resto section '%s' (%s): %s" \
                  % (section, type(ex), str(ex))
            logging.error(error)
            context_dict["error"] = error
            ctxt = Context(context_dict)
            tmpl = loader.get_template("%s/sections/resto_error.html" % Config.APP_NAME)
            return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------
# Post opinion to a resto
#---------------------------------------------------------------------------------------------------

def post_opinion(request, uid):
    """Handles request for posting an opinion on a resto. If the request method is GET, this
    function renders a form for posting the opinion. If the request method is POST, this function
    collects information from the request, adds ratings to the resto, inserts a new comment on the
    resto to the datastore (if necessary), and redirects to the resto's home page.
    
    This function requires that the user be logged in.
    """
    
    # Check that the user is logged in.
    if users.get_current_user() is None:
        logging.warning("Anonymous user should not have been able to invoke post_opinion().")
        error = "Anonymous user should not have been able to post an opinion."
        return _redirect_to_error_page(error)
    
    if request.method == "GET":
        try:
            # Render the post opinion form.
            context_dict = _get_common_context_dict(request)
            ACTIONS = ( ajax.RestoNamesSection,
                        ajax.RestoLogoSection,
                        ajax.RestoDescSection,
                        ajax.RestoAddressSection,
                        ajax.RestoContactSection,
                        ajax.RestoFooterSection, )
            for action_class in ACTIONS:
                action = action_class(uid)
                action.process(context_dict, request)
            ctxt = Context(context_dict)
            tmpl = loader.get_template("%s/post_opinion.html" % Config.APP_NAME)
            return HttpResponse(tmpl.render(ctxt))
        
        except Exception, ex:
            error = "Failed to process request for posting opinion (%s): %s" % (type(ex), str(ex))
            logging.error(error)
            return _redirect_to_error_page(error)
    
    elif request.method == "POST":
        try:
            # Retrieve the resto from datastore.
            resto = Resto.gql("WHERE uid=:uid", uid=uid).get()
            if resto is None:
                logging.warning("Failed to find resto by UID '%s' to post opinion." % uid)
                error = "Failed to find resto by UID '%s' to post opinion."
                return _redirect_to_error_page(error)
            # Add user ratings to the resto.
            cuisine = int(utils.get_param(request.POST, "cuisine"))
            economy = int(utils.get_param(request.POST, "economy"))
            service = int(utils.get_param(request.POST, "service"))
            resto.add_rating(cuisine=cuisine, economy=economy, service=service)
            resto.put()
            # Add user comment to the resto, if comment is available.
            title   = utils.get_param(request.POST, "comment_title")
            content = utils.get_param(request.POST, "comment_content")
            if title is not None and content is not None:
                comment = RestoComment( parent=resto,
                                        resto=resto,
                                        cuisine=cuisine,
                                        economy=economy,
                                        service=service,
                                        title=title,
                                        content=content,
                                        author=users.get_current_user() )
                comment.put()
            # Redirect to the resto's home page.
            url = "%s/resto/%s/" % (Config.URL_PREFIX, resto.uid)
            return HttpResponseRedirect(url)
        
        except Exception, ex:
            logging.error( "Failed to post opinion to resto '%s' (%s): %s"
                         % (resto.name, type(ex), str(ex)) )
            error = "Failed to post opinion to resto '%s': %s" % (resto.name, str(ex))
            return _redirect_to_error_page(error)


#---------------------------------------------------------------------------------------------------
# Search restos
#---------------------------------------------------------------------------------------------------

def search_restos(request, criterion, value):
    """Searches restos by criterion.
    """
    
    SEARCH_RESULTS_PER_PAGE = 10
    try:
        page  = utils.get_param(request.REQUEST, "page")
        query = Resto.gql("WHERE %s=:value" % criterion, value=utils.decode_utf8(value).lower())
        restos = utils.EntityPage(query, per_page=SEARCH_RESULTS_PER_PAGE, number=page)
        context_dict = { "criterion"   : criterion,
                         "value"       : value,
                         "restos"      : restos, }
        context_dict.update(_get_common_context_dict(request))
        ctxt = Context(context_dict)
        tmpl = loader.get_template("%s/resto_list.html" % Config.APP_NAME)
        return HttpResponse(tmpl.render(ctxt))
    except Exception, ex:
        error = "Failed to search by '%s=%s' (%s): %s" % (criterion, value, type(ex), str(ex))
        logging.error(error)
        return _redirect_to_error_page(error)

#---------------------------------------------------------------------------------------------------
# View search results from Google Custom Search
#---------------------------------------------------------------------------------------------------

def view_search_results(request):
    """Displays the search results page. The search engine is powered by Google Custom Search. So
    We have nothing to do here.
    """
    
    vals = _get_common_context_dict(request)
    ctxt = Context(vals)
    tmpl = loader.get_template("%s/search_results.html" % Config.APP_NAME)
    return HttpResponse(tmpl.render(ctxt))


